#include "sprite_set.h"
#include "ui/ui_manager.h"
#include "graphics/surface.h"

#include "graphics/image.h"
#include "graphics/graphics.h"
#include "graphics/graphics_util.h"

#include "resman/resman.h"
#include "script/scripthelper.h"

#include "util/timer.h"				// Needs to come in early
#include "util/mmgr.h"				// Needs to come in early
#include "util/common_macros.h"		// macros
#include "util/log.h"				// logging

#include <algorithm>
#include <cassert>
using namespace ui;
using namespace script;


SpriteSet&	SpriteSet::Instance()
{
	static SpriteSet	Inst;
	return Inst;
}


SpriteSet::SpriteSet() :
	num::NamedSet<SpriteInstance*>(),
	m_DeferLoading(false),
	m_Clamp(0)
{

}

SpriteSet::~SpriteSet()
{
}


void	SpriteSet::Refresh()
{
	res::ResMan::Instance().FlushData();
	for (unsigned int i=0; i<m_SpriteSetsLoaded.size(); i++)
	{
		AddSet(m_SpriteSetsLoaded[i], false);	// don't track it this time, or this becomes an infinite loop!
	}
}

SpriteInstance*		SpriteSet::GetSprite(const std::string& Sprite)
{
	SpriteInstance*	pSprite= GetElement(Sprite);
	if (pSprite && !pSprite->pImage && m_DeferLoading)
	{
		// cache here
		pSprite->Init();
	}
	return pSprite;
}

void				SpriteSet::LoadAtlas(const std::string& File)
{
	std::string Temp;
	LuaPlus::LuaState*	pScript = ScriptHelper::CreateLuaState();
	if (!ScriptHelper::DoFile(*pScript, File))
	{
		_LOG(MSG_ERROR, "Unable to load Atlas set " << File << "!");
		 LuaPlus::LuaState::Destroy( pScript );
		return;
	} else
	{
		LuaPlus::LuaObject	AtlasInfo = ScriptHelper::GetObj(*pScript, "AtlasInfo");
		if (AtlasInfo.IsTable())
		{
			m_AtlasInfo.BaseName = ScriptHelper::GetTableString(AtlasInfo, "Base");
			m_AtlasInfo.Pages = ScriptHelper::GetTableInt(AtlasInfo, "Pages");
			m_AtlasInfo.Width = ScriptHelper::GetTableInt(AtlasInfo, "Width");
			m_AtlasInfo.Height = ScriptHelper::GetTableInt(AtlasInfo, "Height");
		}

		LuaPlus::LuaObject	Atlas = ScriptHelper::GetObj(*pScript, "Atlas");
		if (Atlas.IsTable())
		{
			for (int i=0; i<Atlas.GetTableCount(); i++)
			{
				LuaPlus::LuaObject	NewAtlasEntry = Atlas.GetByIndex(i+1);
				if (NewAtlasEntry.IsTable())
				{
					AtlasEntry*	pEntry = new AtlasEntry;
					SpriteInstance::SetString(pEntry->Name, ScriptHelper::GetTableString(NewAtlasEntry, "Name"));

//#ifdef _PLATFORM_SDL
					// Force to PNG                    
                    
                    
					const char*     File    = pEntry->Name;
					size_t          SL      = strlen(File);
					char	        Ext[]   = { static_cast<char>(tolower(File[SL-3])),
                                                static_cast<char>(tolower(File[SL-2])),
                                                static_cast<char>(tolower(File[SL-1])),
					'\0'};

					if (strcmp(Ext, "dds")==0 || strcmp(Ext, "DDS")==0)
					{
						Temp = vfs::VFS::StripExtension(pEntry->Name) + ".png";
						strcpy( pEntry->Name, Temp.c_str());
					}
//#endif

					pEntry->PageIndex =  ScriptHelper::GetTableInt(NewAtlasEntry, "PageIndex");
					pEntry->x =  ScriptHelper::GetTableInt(NewAtlasEntry, "x");
					pEntry->y =  ScriptHelper::GetTableInt(NewAtlasEntry, "y");
					pEntry->w =  ScriptHelper::GetTableInt(NewAtlasEntry, "w");
					pEntry->h =  ScriptHelper::GetTableInt(NewAtlasEntry, "h");
					m_Atlas.AddElement(pEntry->Name, pEntry);
				}
			}
		}
	}
	LuaPlus::LuaState::Destroy( pScript );
}

void				SpriteSet::AddSet(const std::string& File, bool TrackSet)
{
	std::vector<std::string>		FailedSprites;

	LuaPlus::LuaState*	pScript = ScriptHelper::CreateLuaState();
	if (!ScriptHelper::DoFile(*pScript, File))
	{
		_LOG(MSG_ERROR, "Unable to load Sprite set " << File << "!");
		 LuaPlus::LuaState::Destroy( pScript );
		return;
	} else
	{
		ui::UIManager::Instance().UpdateLoadCB();
		if (TrackSet)
		{
			m_SpriteSetsLoaded.push_back(File);
		}
		LuaPlus::LuaObject	Sprites = ScriptHelper::GetObj(*pScript, "Sprites");
		if (Sprites.IsTable())
		{
			for (int i=0; i<Sprites.GetTableCount(); i++)
			{
				LuaPlus::LuaObject	Sprite = Sprites.GetByIndex(i+1);
				if (Sprite.IsTable())
				{
					std::string	Name = ScriptHelper::GetTableString(Sprite, "Name", "");
					std::string	Image = ScriptHelper::GetTableString(Sprite, "ImageFile", "");


//#ifdef _PLATFORM_SDL

					// Force change to PNG

					const char*     File    = Image.c_str();
					size_t          SL      = strlen(File);
					char	        Ext[]   = {
                        static_cast<char>(tolower(File[SL-3])),
						static_cast<char>(tolower(File[SL-2])),
						static_cast<char>(tolower(File[SL-1])),
					'\0'};

					if (strcmp(Ext, "tga")==0)
					{
						Image = vfs::VFS::StripExtension(Image) + ".png";		
					}
//#endif


					if (SpriteInstance*	 pTest = GetElement(Name))
					{
						_LOG(MSG_ERROR, "Duplicate sprite found " << Name << "(" << Image << "). Already exists as (" << pTest->ImageName);
						continue;			
					}
					
					int	ImageC			= ScriptHelper::GetTableInt(Sprite, "SpriteC", 1);
					int SpriteC_Row     = ScriptHelper::GetTableInt(Sprite, "SpriteC_Row", 1);
					float FrameTime		= ScriptHelper::GetTableFloat(Sprite, "FrameTime", 10);
					float FrameBorderX	= ScriptHelper::GetTableFloat(Sprite, "FrameBorderX", 0);
					float FrameBorderY	= ScriptHelper::GetTableFloat(Sprite, "FrameBorderY", 0);
					bool	FlipU		= ScriptHelper::GetTableBool(Sprite, "FlipU", false);
					bool	FlipV		= ScriptHelper::GetTableBool(Sprite, "FlipV", false);
					bool	ForceCache	= ScriptHelper::GetTableBool(Sprite, "Force", false);
					float ClipX			= ScriptHelper::GetTableFloat(Sprite, "ClipX", 0);
					float ClipY			= ScriptHelper::GetTableFloat(Sprite, "ClipY", 0);


					FrameTime = 1.0f / FrameTime; // convert from FPS to seconds per frame
					if ((ImageC != 0) && !Name.empty() && !Image.empty())
					{
						SpriteInstance*		pSprite = AddSprite(	Name, 
																	Image, 
																	ImageC, 
																	SpriteC_Row,
																	FrameTime, 
																	FrameBorderX, 
																	FrameBorderY, 
																	FlipU, 
																	FlipV, 
																	ClipX, 
																	ClipY, 
																	ForceCache, 
																	m_SpriteSetsLoaded.size()-1);
						if (!pSprite)
						{
							FailedSprites.push_back("Missing Sprite " + Name + " : " + Image);
						} else
						{
							if (pSprite->pImage)	// don't update on rapid fire loading, or we'll spend too long on this
							{
								UIMAN().UpdateLoadCB();
							}
						}
					} 

					if(m_Atlas.GetElement(Image))
					{
						//res::ResMan::Instance().AddUIToMap(Image);
					}
				}
			}
		}
	}

	for (unsigned int i = 0; i<FailedSprites.size(); i++)
	{
		_LOG(MSG_COMMENT, FailedSprites[i]);
	}

	LuaPlus::LuaState::Destroy( pScript );
}

SpriteInstance*	SpriteSet::AddSprite(	const std::string& Name, 
										const std::string& Image, 
										int SpriteC, 
										int SpriteC_Row,
										float FrameTime, 
										float BorderX, 
										float BorderY, 
										bool FlipU, 
										bool FlipV, 
										float ClipX, 
										float ClipY, 
										bool	ForceCache, 
										int Source)
{
	SpriteInstance*	pSprite = GetElement(Name);
	if (!pSprite)
	{	
		// new one
		pSprite = new SpriteInstance();
	} else
	{
		return NULL;
	}
	
	pSprite->SetString(pSprite->Name, Name);
	AtlasEntry*	pEntry		= m_Atlas.GetElement(Image);
	pSprite->SpriteC		= abs(SpriteC);
	pSprite->SpriteC_Row	= abs(SpriteC_Row );
	pSprite->FrameTime	= (FrameTime < 0.0f) ? -FrameTime : FrameTime;
	pSprite->ForceCache	= ForceCache;
	pSprite->Source		= Source;
	pSprite->FlipX		= FlipU;
	pSprite->FlipY		= FlipV;
	pSprite->VertFrames = SpriteC<0;

	if (pEntry)
	{
//#ifdef _PLATFORM_SDL
		sprintf(pSprite->ImageName, "%s%02i.png", m_AtlasInfo.BaseName.c_str(), pEntry->PageIndex);

//#else	
//		sprintf(pSprite->ImageName, "%s%02i.tga", m_AtlasInfo.BaseName.c_str(), pEntry->PageIndex);
		
//#endif
		pSprite->UV1.x = (pEntry->x+1) / (float)m_AtlasInfo.Width;
		pSprite->UV1.y = (pEntry->y+1) / (float)m_AtlasInfo.Height;
		pSprite->UV2.x = (pEntry->x+pEntry->w-1) / (float)m_AtlasInfo.Width;
		pSprite->UV2.y = (pEntry->y+pEntry->h-1) / (float)m_AtlasInfo.Height;
		pSprite->pAtlasEntry = pEntry;
	} else
	{
		pSprite->SetString(pSprite->ImageName, Image);

		pSprite->ClipX = ClipX;
		pSprite->ClipY = ClipY;
	}

// defer loading
	if (m_DeferLoading && !ForceCache)
	{
		pSprite->pImage		= NULL;
		AddElement(pSprite->Name, pSprite);
		return pSprite;
	} else
	{
	// immediate loading
		pSprite->Init();
		if (pSprite->pImage)
		{
			AddElement(pSprite->Name, pSprite);
			return pSprite;
		}
		else
		{
			UTIL_SAFE_DELETE(pSprite);
			return NULL;
		}
	}

	return NULL;	// how did that happen?
}
	

bool	SpriteInstance::Init()
{
// lazy evaluation
	if (!pImage && !Failed)	
	{
		pImage		= GetGraphics()->LoadImageFromFile(ImageName);
		if (pImage)
		{			
			float	w = (float)pImage->GetWidth();
			float	h = (float)pImage->GetHeight();

			float		Edge = (float)SpriteSet::Instance().GetClamp();
			Edge = 0;
			if (this->SpriteC > 1)
			{
				Edge = 0.00f;
			}
			// update UV2
			if (!pAtlasEntry)
			{
				UV1.x = Edge/w;
				UV1.y = Edge/h;
				UV2.x = (w-(ClipX+Edge)) / w;
				UV2.y = (h-(ClipY+Edge)) / h;
			}

			if (FlipX)
			{
				float t = UV2.x;
				UV2.x = UV1.x;
				UV1.x = t;
			}
			if (FlipY)
			{
				float t = UV2.y;
				UV2.y = UV1.y;
				UV1.y = t;
			}
		} else
		{
			Failed = true;
		}
	}
	return true;
}

void	SpriteInstance::Flush()
{
	if (pImage && !ForceCache)	// some things shouldn't be uncached.
	{	
		res::ResMan::Instance().ReleaseResource(pImage->GetFile());
		pImage = NULL;	// force it to refetch this later
	}	
	UTIL_SAFE_DELETE(m_pMask);
}

AlphaMask*		SpriteInstance::GenerateMask()
{
	UTIL_SAFE_DELETE(m_pMask);
	m_pMask = pImage->GetSurface()->GenerateMask();
	return m_pMask;
}


bool	SpriteInstance::UpdateTime(int& LoopC, float& time, float dt, bool loop)
{
	const float TotalTime = GetAnimTime();
	bool res = true;
	if (loop) 
	{
		time += dt;
		// sprite animation always moves in complete cycles.
		if (time >= TotalTime)
		{
			time = 0;
			LoopC++;
			return false;
		}
	}
	else {
		if (time < TotalTime) {
			time += dt;
			//if (time > TotalTime) { // ADRIAN: very, very, very RARE conditional bug may occur here -- and I just hit it! :o
			if (time >= TotalTime) {
				time = TotalTime;
				res = false || SpriteC <= 1;
				LoopC++;
			}
		}
	}
	return res;
}

void	SpriteInstance::GetFrameSize(math::Vec2& Size)
{
	if (pImage)
	{
		if (VertFrames)
		{
			Size.x = float(pImage->GetWidth() * (UV2.x-UV1.x))/float(GraphicsUtil::WIDTH);
			Size.y = float(pImage->GetHeight() * (UV2.y-UV1.y))/float(SpriteC * GraphicsUtil::HEIGHT);
		} else
		{
			int ColC = SpriteC/SpriteC_Row;
			Size.x = float(pImage->GetWidth() * (UV2.x-UV1.x))/float(ColC * GraphicsUtil::WIDTH);
			Size.y = float(pImage->GetHeight() * (UV2.y-UV1.y))/float(SpriteC_Row * GraphicsUtil::HEIGHT);
		}
		Size.x = fabs(Size.x);
		Size.y = fabs(Size.y);

	}
}	


void	SpriteInstance::GetUV(float Time, math::Vec2& UV1, math::Vec2& UV2)
{
	unsigned int Frame = (int)(Time/FrameTime);
	GetUV(Frame, UV1, UV2);
}

void  SpriteInstance::GetUV(unsigned int FrameID, math::Vec2& _UV1, math::Vec2& _UV2)
{
	// is it worth pre-calcing this?
	if (!pImage)
	{
		return;
	}
	if (SpriteC<=1)
	{
		_UV1 = UV1;
		_UV2 = UV2;
		return;	// fast exit path
	}

	int ColC = SpriteC/SpriteC_Row;

	unsigned int	Frame = FrameID % (ColC);
	unsigned int	Frame_Row = FrameID / ColC;	

	float			t1 = Frame/(float)ColC;
	float			t2 = (Frame+1)/(float)ColC;

	float			tr_1 = Frame_Row/(float)SpriteC_Row;	
	float			tr_2 = (Frame_Row+1)/(float)SpriteC_Row;	

	if (!VertFrames)
	{
		_UV1.x = UV1.x + ((UV2.x-UV1.x)*t1);
		_UV1.y = UV1.y + ((UV2.y-UV1.y)*tr_1);;
		_UV2.x = UV1.x + ((UV2.x-UV1.x)*t2);
		_UV2.y = UV1.y + ((UV2.y-UV1.y)*tr_2);;
	} else
	{
		_UV1.x = UV1.x ;
		_UV1.y = UV1.y+ ((UV2.y-UV1.y)*t1);
		_UV2.x = UV2.x ;
		_UV2.y = UV1.y+ ((UV2.y-UV1.y)*t2);
	}
}
